/**
 * Progetto PRAISED.
 * 11/ott/2010
 */
package it.uniroma3.dia.praised.brenda.crawler;

import it.uniroma3.dia.praised.brenda.model.Enzyme;
import it.uniroma3.dia.praised.brenda.parser.ParserBrenda;
import it.uniroma3.dia.praised.exception.CrawlerException;
import it.uniroma3.dia.praised.util.Utils;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * CrawlerBrenda: Classe che modella il crawler relativo alla pagina web 
 * http://www.brenda-enzymes.org/index.php4?page=information/all_enzymes.php4?ecno
 * A partire da questa pagina recupera l'elenco di tutti gli enzimi e li memorizza nel database
 * 
 * @author Accosta Augusto
 * @author Lopez Elisabetta
 * 
 * @see it.uniroma3.dia.praised.brenda.parser.Parser
 * @see it.uniroma3.dia.praised.brenda.parser.ParserBrenda
 * @see it.uniroma3.dia.praised.brenda.model.web.WebPageEnzima
 * @see it.uniroma3.dia.praised.brenda.validator.Validator
 * @version 1.0
 *
 */
public class CrawlerBrenda {

	private final URL root; // Site URL

	private static File file = new File("input.txt"); /* Contains all the links to be managed by our crawler */
	private static File badListFile = new File("badlist.txt"); /* Contains the link that failing during the process  */
	
	private ConcurrentLinkedQueue<URL> badList = new ConcurrentLinkedQueue<URL>();
	
	
	/**
	 * Costruttore di default della classe CrawlerBrenda 
	 */
	public CrawlerBrenda(URL root) {
		this.root = root;
	}
	
	/**
	 * Inizializza i valori delle variabili di classe e recupera i link presenti nella root del sito web
	 * gestendo opportuni thread
	 */
	public void startup() throws CrawlerException {
		/* Creazione del Parser e recupero dei link che popolano la lista di URL */
		ParserBrenda parser = new ParserBrenda();
		
		List<URL> listURL = parser.getHref(this.root);
		
		/* Scrittura nel file dei link appena creati per verificarne la corretta estrazione */
		Utils.writeIntoFile(file, listURL);
		
		/* Creazione dei thread che gestiscono i worker del crawler */
		this.manageThreads(listURL);
		
		Utils.writeIntoFile(badListFile, this.badList);
	}
	
	/**
	 * Metodo che gestisce un pool di thread utilizzando LEF e {@link http://download.oracle.com/javase/6/docs/api/java/util/concurrent/package-summary.html}.
	 * Ogni thread gestisce un URL preso dalla lista di tutti gli URL.
	 * Per ogni URL gestito, il rispettivo thread crea un'istanza della classe CrawlerBrendaWorker a cui è
	 * delegata la responsabilità di creare l'oggetto Enzima con tutte le sue proprietà.
	 * 
	 * TODO: Si può gestire anche un risultato del processamento degli URL.
	 *    
	 * @param listURL - lista degli url estratti
	 * @throws CrawlerException
	 */
	private void manageThreads(List<URL> listURL) throws CrawlerException {
		ExecutorService pool = Executors.newFixedThreadPool(Utils.getNumeroCPUSimulate());
		List<Future<Enzyme>> enzymesFuture = new ArrayList<Future<Enzyme>>(listURL.size());

		for (URL url : listURL.subList(5000, listURL.size())) { //.subList(0, 100)
			enzymesFuture.add(pool.submit(new CrawlerBrendaWorker(url, this.badList)));
		}
		
		List<Enzyme> enzymes = new ArrayList<Enzyme>(enzymesFuture.size());
		int interrupted = 0;
		int executionException = 0;
		Iterator<Future<Enzyme>> it =  enzymesFuture.iterator();
		while (it.hasNext()) {
			Future<Enzyme> future = it.next();
			try {
				enzymes.add(future.get());
			} catch (InterruptedException e) {
				e.printStackTrace();
				interrupted++;
			} catch (ExecutionException e) {
				e.printStackTrace();
				executionException++;
			}
			it.remove();
		}
	
		System.out.println("NUMBER OF CRAWLED ENZYMES: " + (enzymes.size() - this.badList.size()));
		System.out.println("Interrupted Exception = " + interrupted);
		System.out.println("Execution Exception = " + executionException);
		
//		pool.shutdown();
		shutdownAndAwaitTermination(pool);
	}
	
	void shutdownAndAwaitTermination(ExecutorService pool) {
		pool.shutdown(); // Disable new tasks from being submitted
		try {
			// Wait a while for existing tasks to terminate
			if (!pool.awaitTermination(200, TimeUnit.SECONDS)) {
				pool.shutdownNow(); // Cancel currently executing tasks
				// Wait a while for tasks to respond to being cancelled
				if (!pool.awaitTermination(200, TimeUnit.SECONDS))
					System.err.println("Pool did not terminate");
			}
		} catch (InterruptedException ie) {
			// (Re-)Cancel if current thread also interrupted
			pool.shutdownNow();
			// Preserve interrupt status
			Thread.currentThread().interrupt();
		}
	}
 
	
}